package cn.thornbird.orgsync.helper;

import cn.thornbird.orgsync.entity.status.AppType;
import cn.thornbird.orgsync.model.AppUserData;
import cn.thornbird.orgsync.model.IResponse;
import cn.thornbird.orgsync.model.TreeData;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public abstract class AppHelper<U> {

    protected static HttpComponentsClientHttpRequestFactory clientHttpRequestFactory;

    static {
        clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        clientHttpRequestFactory.setConnectTimeout(30000);
        clientHttpRequestFactory.setReadTimeout(30000);
    }

    protected AppType currentAppType;

    protected RedisTemplate<String, String> redisTemplate;

    protected String certificateText;

    protected List<Long> timestamps;

    protected int maxFrequency = 300;

    protected boolean handleBadRequestError = false;

    protected List<U> allUsers;

    private Loggable requestLogger;

    private RestClientException lastError;

    public void initialize(RedisTemplate<String, String> redisTemplate, String certificateText) {
        this.redisTemplate = redisTemplate;
        this.certificateText = certificateText;
        this.timestamps = new ArrayList<>();
    }

    public RestClientException getLastError() {
        return lastError;
    }

    public AppUserData findAppUser(List<AppUserData> users, AppUserData user) {
        AppUserData matchedUser = null;
        for (AppUserData u : users) {
            if (!StringUtils.isEmpty(u.getEmployeeNo()) && u.getEmployeeNo().equals(user.getEmployeeNo())) {
                matchedUser = u;
                break;
            } else if (!StringUtils.isEmpty(u.getEmail()) && u.getEmail().equalsIgnoreCase(user.getEmail())) {
                matchedUser = u;
                break;
            } else if (!StringUtils.isEmpty(u.getMobile()) && u.getMobile().equals(user.getMobile())) {
                matchedUser = u;
                break;
            } else if (!StringUtils.isEmpty(u.getTelephone()) && u.getTelephone().equals(user.getTelephone())) {
                matchedUser = u;
                break;
            }
        }
        return matchedUser;
    }

    public abstract String getToken();

    public abstract List<TreeData> getOrganizationTree();

    public abstract List<AppUserData> getUsersByDepartmentId(String departmentId);

    public abstract AppUserData findAppUser(AppUserData sourceUser);

    public abstract AppUserData findAppUserById(String id);

    public U findUser(List<U> users, UserAttr userAttr, Object value) {
        return findUser(users, userAttr, value, false);
    }

    public U findUser(List<U> users, UserAttr userAttr, Object value, boolean nonUniqueFlag) {
        U user = null;
        if (users == null && allUsers != null) {
            users = allUsers;
        }
        if (users == null || userAttr == null || value == null || StringUtils.isEmpty(value.toString())) {
            return user;
        }

        allUsers = users;
        List<U> matchedUsers = new ArrayList<>();
        for (U item : users) {
            if (checkUser(item, userAttr, value)) {
                matchedUsers.add(item);
                if (!nonUniqueFlag) {
                    break;
                }
            }
        }
        if (matchedUsers.size() == 1) {
            user = matchedUsers.get(0);
        }
        return user;
    }

    public <T> T doGet(String url, Map<String, Object> uriVariables, Class<T> clazz) {
        T result = null;
        RestTemplate restTemplate = getRestTemplate();
        checkFrequency();
        lastError = null;
        writeRequestLog("GET", url, null, uriVariables);
        try {
            if (uriVariables == null) {
                result = restTemplate.getForObject(url, clazz);
            } else {
                result = restTemplate.getForObject(url, clazz, uriVariables);
            }
            writeResultLog(result);
        } catch (RestClientException e) {
            handleRestClientException(e);
        }
        return result;
    }

    public <T> T doGet(String url, HttpHeaders headers, Map<String, Object> uriVariables, Class<T> clazz) {
        T result = null;
        RestTemplate restTemplate = getRestTemplate();
        HttpEntity<String> entity = new HttpEntity<>(headers);
        checkFrequency();
        lastError = null;
        writeRequestLog("GET", url, headers, uriVariables);
        try {
            if (uriVariables == null) {
                result = restTemplate.exchange(url, HttpMethod.GET, entity, clazz).getBody();
            } else {
                result = restTemplate.exchange(url, HttpMethod.GET, entity, clazz, uriVariables).getBody();
            }
            writeResultLog(result);
        } catch (RestClientException e) {
            handleRestClientException(e);
        }
        return result;
    }

    public <T> T doPost(String url, HttpHeaders headers, Map<String, Object> params, Class<T> clazz) {
        T result = null;
        RestTemplate restTemplate = getRestTemplate();
        HttpEntity<Map<String, ?>> request = new HttpEntity<>(params, headers);
        checkFrequency();
        lastError = null;
        writeRequestLog("POST", url, headers, params);
        try {
            result = restTemplate.postForObject(url, request, clazz);
            writeResultLog(result);
        } catch (RestClientException e) {
            handleRestClientException(e);
        }
        return result;
    }

    public <T> T doPatch(String url, HttpHeaders headers, Map<String, Object> params, Class<T> clazz) {
        T result = null;
        RestTemplate restTemplate = getRestTemplate();
        HttpEntity<Map<String, ?>> request = new HttpEntity<>(params, headers);
        checkFrequency();
        lastError = null;
        writeRequestLog("PATCH", url, headers, params);
        try {
            result = restTemplate.patchForObject(url, request, clazz);
            writeResultLog(result);
        } catch (RestClientException e) {
            handleRestClientException(e);
        }
        return result;
    }

    public <T> T doDelete(String url, HttpHeaders headers, Map<String, Object> uriVariables, Class<T> clazz) {
        T result = null;
        RestTemplate restTemplate = getRestTemplate();
        HttpEntity<String> entity = new HttpEntity<>(headers);
        checkFrequency();
        lastError = null;
        writeRequestLog("DELETE", url, headers, uriVariables);
        try {
            if (uriVariables == null) {
                result = restTemplate.exchange(url, HttpMethod.DELETE, entity, clazz).getBody();
            } else {
                result = restTemplate.exchange(url, HttpMethod.DELETE, entity, clazz, uriVariables).getBody();
            }
            writeResultLog(result);
        } catch (RestClientException e) {
            handleRestClientException(e);
        }
        return result;
    }

    public RestTemplate getRestTemplate() {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public boolean hasError(ClientHttpResponse response) throws IOException {
                if (handleBadRequestError && response.getStatusCode().value() == HttpStatus.BAD_REQUEST.value()) {
                    return false;
                }
                return super.hasError(response);
            }

            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (handleBadRequestError && response.getStatusCode().value() == HttpStatus.BAD_REQUEST.value()) {
                    return;
                }
                super.handleError(response);
            }
        });
        return restTemplate;
    }

    public void clearAllUsers() {
        this.allUsers = null;
    }

    public void setRequestLogger(Loggable requestLogger) {
        this.requestLogger = requestLogger;
    }

    public static AppHelper getHelper(AppType appType) {
        AppHelper helper = null;
        switch (appType) {
            case Netease:
                helper = new NeteaseHelper();
                break;
            case Wechat:
                helper = new WechatHelper();
                break;
            case DingTalk:
                helper = new DingTalkHelper();
                break;
            case Feishu:
                helper = new FeishuHelper();
                break;
            default:
                break;
        }
        if (helper != null) {
            helper.currentAppType = appType;
        }
        return helper;
    }

    protected void handleRestClientException(RestClientException e) {
        lastError = e;
        log.error("error", e);
        writeErrorLog(e);
    }

    protected void writeRequestLog(String method, String url, HttpHeaders headers, Map<String, Object> data) {
        if (requestLogger != null) {
            Map<String, Object> request = new HashMap<>();
            request.put("method", method);
            request.put("url", url);
            request.put("headers", headers);
            request.put("data", data);
            requestLogger.writeLog(false, "开始api请求: {0}", requestLogger.toJsonString(request));
        }
    }

    protected void writeResultLog(Object result) {
        if (requestLogger != null) {
            Boolean success = null;
            if (result instanceof IResponse) {
                success = ((IResponse) result).isSuccess();
            }
            String apiResult = success != null ? (success ? "成功" : "失败") : "";
            requestLogger.writeLog(false, "api请求返回结果: {0}{1}",
                    apiResult, requestLogger.toJsonString(result));
        }
    }

    protected void writeErrorLog(RestClientException e) {
        if (requestLogger != null) {
            requestLogger.writeLog(true, "api请求产生错误: {0}", e.getMessage());
        }
    }

    protected void checkFrequency() {
        while (timestamps.size() > maxFrequency) {
            long now = System.currentTimeMillis();
            while (timestamps.size() > 0 && timestamps.get(0) < now - 60 * 1000) {
                timestamps.remove(0);
            }
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        timestamps.add(System.currentTimeMillis());
    }

    protected String formatUri(String uriTemplate, String token) {
        Map<String, String> uriVariables = new HashMap<>();
        uriVariables.put("access_token", token);
        return new DefaultUriBuilderFactory().expand(uriTemplate, uriVariables).toString();
    }

    protected String formatUri(String uriTemplate, String name, String value) {
        Map<String, String> uriVariables = new HashMap<>();
        uriVariables.put(name, value);
        return new DefaultUriBuilderFactory().expand(uriTemplate, uriVariables).toString();
    }

    protected boolean hasScheme(String uri) {
        return uri.toLowerCase().startsWith("https://") || uri.toLowerCase().startsWith("http://");
    }

    protected boolean checkUser(U user, UserAttr userAttr, Object value) {
        return false;
    }

    protected boolean compareMobile(String mobile1, String mobile2) {
        if (mobile1 == null || mobile2 == null) {
            return false;
        }
        return getPureMobile(mobile1).equals(getPureMobile(mobile2));
    }

    public static String getPureMobile(String mobile) {
        if (mobile == null) {
            return null;
        }
        mobile = mobile.trim();
        return mobile.startsWith("+86") ? mobile.substring(3) : mobile;
    }


    public enum UserAttr {

        ID(0),
        EMPLOYEE_NO(1),
        NAME(2),
        MOBILE(3),
        EMAIL(4),
        ENTERPRISE_EMAIL(5);

        private final int value;

        UserAttr(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }

    }

}
